﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using Tesla.Graphics;
using Tesla.Math;

namespace Tesla.Core {
    /// <summary>
    /// Encapsulates the engine value properties, which is a centralized way of binding and serving shader uniform
    /// data. For this data map to properly work, a camera and timer must be set before values are requested.
    /// </summary>
    public sealed class EngineValueMap {
        private ICamera _activeCam;
        private GameTimer _activeTimer;
        private Matrix _worldMatrix;
        private Random _random;

        /// <summary>
        /// Gets or sets the current camera bound to the value map. This is required to use any view or projection
        /// matrix related properties (e.g. rendering geometry).
        /// </summary>
        public ICamera Camera {
            get {
                return _activeCam;
            }
            set {
                _activeCam = value;
            }
        }

        /// <summary>
        /// Gets or sets the current game timer bound to the value map. This is required to use time-related properties.
        /// </summary>
        public GameTimer Timer {
            get {
                return _activeTimer;
            }
            set {
                _activeTimer = value;
            }
        }

        /// <summary>
        /// Gets or sets the current world matrix. Set this before geometry is to be rendered in order for it to be
        /// used in sending data to the shader.
        /// </summary>
        public Matrix WorldMatrix {
            get {
                return _worldMatrix;
            }
            set {
                _worldMatrix = value;
            }
        }

        /// <summary>
        /// Gets the current camera's view matrix.
        /// </summary>
        public Matrix ViewMatrix {
            get {
                if(_activeCam != null) {
                    return _activeCam.ViewMatrix;
                }
                return Matrix.Identity;
            }
        }

        /// <summary>
        /// Gets the current camera's projection matrix.
        /// </summary>
        public Matrix ProjectionMatrix {
            get {
                if(_activeCam != null) {
                    return _activeCam.ProjectionMatrix;
                }
                return Matrix.Identity;
            }
        }

        /// <summary>
        /// Gets the World-View concatenated matrix.
        /// </summary>
        public Matrix WorldViewMatrix {
            get {
                Matrix view = Matrix.Identity;
                if(_activeCam != null) {
                    view = _activeCam.ViewMatrix;
                }
                Matrix worldView;
                Matrix.Multiply(ref _worldMatrix, ref view, out worldView);
                return worldView;
            }
        }

        /// <summary>
        /// Gets the View-Projection concatenated matrix.
        /// </summary>
        public Matrix ViewProjectionMatrix {
            get {
                Matrix viewProj = Matrix.Identity;
                if(_activeCam != null) {
                    viewProj = _activeCam.ViewProjectionMatrix;
                }
                return viewProj;
            }
        }

        /// <summary>
        /// Gets the World-View-Projection concatenated matrix.
        /// </summary>
        public Matrix WorldViewProjection {
            get {
                Matrix view = Matrix.Identity;
                Matrix proj = Matrix.Identity;
                if(_activeCam != null) {
                    view = _activeCam.ViewMatrix;
                    proj = _activeCam.ProjectionMatrix;
                }
                Matrix worldView;
                Matrix worldViewProj;
                Matrix.Multiply(ref _worldMatrix, ref view, out worldView);
                Matrix.Multiply(ref worldView, ref proj, out worldViewProj);
                return worldViewProj;
            }
        }

        /// <summary>
        /// Gets the World matrix's inverse transpose, otherwise known as the Normal Matrix that is used
        /// to correctly transform Normal vectors to world space when non-uniform scaling is used for lighting.
        /// </summary>
        public Matrix WorldInverseTranspose {
            get {
                Matrix worldInverse;
                Matrix worldInverseTranspose;

                Matrix.Invert(ref _worldMatrix, out worldInverse);
                Matrix.Transpose(ref worldInverse, out worldInverseTranspose);

                return worldInverseTranspose;
            }
        }

        /// <summary>
        /// Gets the inverse of the world matrix, which takes us from world-space to model-space.
        /// </summary>
        public Matrix WorldMatrixInverse {
            get {
                Matrix worldInverse;
                Matrix.Invert(ref _worldMatrix, out worldInverse);
                return worldInverse;
            }
        }

        /// <summary>
        /// Gets the inverse of the view matrix, which takes us from view-space to world-space.
        /// </summary>
        public Matrix ViewMatrixInverse {
            get {
                if(_activeCam != null) {
                    Matrix view = _activeCam.ViewMatrix;
                    Matrix viewInverse;
                    Matrix.Invert(ref view, out viewInverse);
                    return viewInverse;
                }
                return Matrix.Identity;
            }
        }

        /// <summary>
        /// Gets the inverse of the projection matrix, which takes us from projection-space to view-space.
        /// </summary>
        public Matrix ProjectionMatrixInverse {
            get {
                if(_activeCam != null) {
                    Matrix proj = _activeCam.ProjectionMatrix;
                    Matrix projInverse;
                    Matrix.Invert(ref proj, out projInverse);
                    return projInverse;
                }
                return Matrix.Identity;
            }
        }

        /// <summary>
        /// Gets the dimensions of the currently active viewport, represented by a Vector4. X = Viewport.X,
        /// Y = Viewport.Width, Z = Viewport.Y, W = Viewport.Height.
        /// </summary>
        public Vector4 Viewport {
            get {
                if(_activeCam != null) {
                    Viewport viewport = _activeCam.Viewport;
                    return new Vector4(viewport.X, viewport.Width, viewport.Y, viewport.Height);
                }
                return Vector4.Zero;
            }
        }


        /// <summary>
        /// Gets the resolution of the viewport, represented by a Vector2. X = Width, Y = Height.
        /// </summary>
        public Vector2 Resolution {
            get {
                if(_activeCam != null) {
                    Viewport viewport = _activeCam.Viewport;
                    return new Vector2(viewport.Width, viewport.Height);
                }
                return Vector2.Zero;
            }
        }

        /// <summary>
        /// Gets Near and Far values of the view frustum of the currently active camera,
        /// represented by a Vector2. X = Near, Y = Far.
        /// </summary>
        public Vector2 FrustumNearFar {
            get {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Gets the aspect ratio, float value equal to Width/Height of the currently active camera's
        /// viewport.
        /// </summary>
        public float AspectRatio {
            get {
                if(_activeCam != null) {
                    Viewport viewport = _activeCam.Viewport;
                    return viewport.AspectRatio;
                }
                return 0;
            }
        }

        /// <summary>
        /// Gets the position of the currently active camera in world space.
        /// </summary>
        public Vector3 CameraPosition {
            get {
                if(_activeCam != null) {
                    return _activeCam.Position;
                }
                return Vector3.Zero;
            }
        }

        /// <summary>
        /// Gets the direction (Forward) of the currently active camera.
        /// </summary>
        public Vector3 CameraDirection {
            get {
                if(_activeCam != null) {
                    return _activeCam.Direction;
                }
                return Vector3.Zero;
            }
        }

        /// <summary>
        /// Gets the left vector of the currently active camera.
        /// </summary>
        public Vector3 CameraRight {
            get {
                if(_activeCam != null) {
                    return _activeCam.Right;
                }
                return Vector3.Zero;
            }
        }

        /// <summary>
        /// Gets the up vector of the currently active camera.
        /// </summary>
        public Vector3 CameraUp {
            get {
                if(_activeCam != null) {
                    return _activeCam.Up;
                }
                return Vector3.Zero;
            }
        }

        /// <summary>
        /// Gets the total time in seconds since the application was started (or since the game timer was set). 
        /// </summary>
        public float Time {
            get {
                if(_activeTimer != null) {
                    return (float) _activeTimer.GameTime.TotalGameTime.TotalSeconds;
                }
                return 0;
            }
        }

        /// <summary>
        /// Gets the time in seconds since the last frame was rendered.
        /// </summary>
        public float TimePerFrame {
            get {
                if(_activeTimer != null) {
                    return (float) _activeTimer.GameTime.ElapsedTimeInSeconds;
                }
                return 0;
            }
        }

        /// <summary>
        /// Gets the frames per second, which is the inverse of time per frame.
        /// </summary>
        public float FrameRate {
            get {
                if(_activeTimer != null) {
                    return 1.0f / (float)_activeTimer.GameTime.ElapsedTimeInSeconds;
                }
                return 0;
            }
        }

        /// <summary>
        /// Gets the next random float from the engine's random value stream, between 0.0f and 1.0f.
        /// </summary>
        public float RandomFloat {
            get {
                return (float) _random.NextDouble();
            }
        }

        /// <summary>
        /// Gets the next random int (non-negative) from the engine's random value stream.
        /// </summary>
        public int RandomInt {
            get {
                return _random.Next();
            }
        }

        /// <summary>
        /// Gets the random number generator.
        /// </summary>
        public Random Random {
            get {
                return _random;
            }
        }

        /// <summary>
        /// Creates a new engine value map instance.
        /// </summary>
        public EngineValueMap() {
            _random = new Random();
        }

        /// <summary>
        /// Creates a new random number generator with the specified seed value.
        /// </summary>
        /// <param name="seed">Seed value</param>
        public void SetSeedValue(int seed) {
            _random = new Random(seed);
        }

        /// <summary>
        /// Gets the next non-negative random value less than the specified max value.
        /// </summary>
        /// <param name="maxValue">Max value</param>
        /// <returns>Non-negative random int</returns>
        public int NextRandomInt(int maxValue) {
            return _random.Next(maxValue);
        }

        /// <summary>
        /// Gets the next non-negative random value between the low and high values.
        /// </summary>
        /// <param name="low">Minimum value</param>
        /// <param name="high">Maximum value</param>
        /// <returns>Non-negative random int in the low-high range</returns>
        public int NextRandomInt(int low, int high) {
            return _random.Next(low, high);
        }

        /// <summary>
        /// Convenience method for retrieving the int value corresponding to the specified engine value.
        /// </summary>
        /// <param name="valueEnum">EngineValue enum</param>
        /// <param name="value">int value to store result</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the engine value specified does not correspond to an int value.</exception>
        public void GetValue(EngineValue valueEnum, out int value) {
            switch(valueEnum) {
                case EngineValue.RandomInt:
                    value = this.RandomInt;
                    break;
                default:
                    throw new InvalidOperationException("Engine value is not an int!");
            }
        }

        /// <summary>
        /// Convenience method for retrieving the float value corresponding to the specified engine value.
        /// </summary>
        /// <param name="valueEnum">EngineValue enum</param>
        /// <param name="value">float value to store result</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the engine value specified does not correspond to a float value.</exception>
        public void GetValue(EngineValue valueEnum, out float value) {
            switch(valueEnum) {
                case EngineValue.AspectRatio:
                    value = this.AspectRatio;
                    break;
                case EngineValue.Time:
                    value = this.Time;
                    break;
                case EngineValue.TimePerFrame:
                    value = this.TimePerFrame;
                    break;
                case EngineValue.FrameRate:
                    value = this.FrameRate;
                    break;
                case EngineValue.RandomFloat:
                    value = this.RandomFloat;
                    break;
                default:
                    throw new InvalidOperationException("Engine value is not a float!");
            }
        }

        /// <summary>
        /// Convenience method for retrieving the Vector2 value corresponding to the specified engine value.
        /// </summary>
        /// <param name="valueEnum">EngineValue enum</param>
        /// <param name="value">Vector2 value to store result</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the engine value specified does not correspond to a Vector2 value.</exception>
        public void GetValue(EngineValue valueEnum, out Vector2 value) {
            switch(valueEnum) {
                case EngineValue.Resolution:
                    value = this.Resolution;
                    break;
                case EngineValue.FrustumNearFar:
                    value = this.FrustumNearFar;
                    break;
                default:
                    throw new InvalidOperationException("Engine value is not a Vector2!");
            }
        }

        /// <summary>
        /// Convenience method for retrieving the Vector3 value corresponding to the specified engine value.
        /// </summary>
        /// <param name="valueEnum">EngineValue enum</param>
        /// <param name="value">Vector3 value to store result</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the engine value specified does not correspond to a Vector3 value.</exception>
        public void GetValue(EngineValue valueEnum, out Vector3 value) {
            switch(valueEnum) {
                case EngineValue.CameraDirection:
                    value = this.CameraDirection;
                    break;
                case EngineValue.CameraRight:
                    value = this.CameraRight;
                    break;
                case EngineValue.CameraPosition:
                    value = this.CameraPosition;
                    break;
                case EngineValue.CameraUp:
                    value = this.CameraUp;
                    break;
                default:
                    throw new InvalidOperationException("Engine value is not a Vector3!");
            }
        }

        /// <summary>
        /// Convenience method for retrieving the Vector4 value corresponding to the specified engine value.
        /// </summary>
        /// <param name="valueEnum">EngineValue enum</param>
        /// <param name="value">fVector4 value to store result</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the engine value specified does not correspond to a Vector4 value.</exception>
        public void GetValue(EngineValue valueEnum, out Vector4 value) {
            switch(valueEnum) {
                case EngineValue.Viewport:
                    value = this.Viewport;
                    break;
                default:
                    throw new InvalidOperationException("Engine value is not a Vector4!");
            }
        }

        /// <summary>
        /// Convenience method for retrieving the Matrix value corresponding to the specified engine value.
        /// </summary>
        /// <param name="valueEnum">EngineValue enum</param>
        /// <param name="value">Matrix value to store result</param>
        /// <exception cref="System.InvalidOperationException">Thrown if the engine value specified does not correspond to a Matrix value.</exception>
        public void GetValue(EngineValue valueEnum, out Matrix value) {
            switch(valueEnum) {
                case EngineValue.WorldMatrix:
                    value = this.WorldMatrix;
                    break;
                case EngineValue.WorldMatrixInverse:
                    value = this.WorldMatrixInverse;
                    break;
                case EngineValue.WorldInverseTranspose:
                    value = this.WorldInverseTranspose;
                    break;
                case EngineValue.ViewMatrix:
                    value = this.ViewMatrix;
                    break;
                case EngineValue.ViewMatrixInverse:
                    value = this.ViewMatrixInverse;
                    break;
                case EngineValue.ProjectionMatrix:
                    value = this.ProjectionMatrix;
                    break;
                case EngineValue.ProjectionMatrixInverse:
                    value = this.ProjectionMatrixInverse;
                    break;
                case EngineValue.WorldViewMatrix:
                    value = this.WorldViewMatrix;
                    break;
                case EngineValue.ViewProjectionMatrix:
                    value = this.ViewProjectionMatrix;
                    break;
                case EngineValue.WorldViewProjection:
                    value = this.WorldViewProjection;
                    break;
                default:
                    throw new InvalidOperationException("Engine value is not a Matrix!");
            }
        }
    }
}
